﻿using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;

namespace LogNaga.Logs
{
	/// <summary>
	/// 失败后使用备用log的decorator
	/// </summary>
	/// <typeparam name="TSource"><see cref="TSource"/>要使用的<see cref="Log"/>实例</typeparam>
	/// <typeparam name="TDestination"><see cref="TDestination"/>备用的<see cref="Log"/>实例</typeparam>
	public class FailoverLogDecorator<TSource, TDestination> : Log
		where TSource : Log, ITransformable
		where TDestination : Log
	{
		private bool useDestinationLog;

		private readonly TSource sourceLog;
		private readonly TDestination destinationLog;
		private readonly long failureTimesLimit;
		private long failedTimes;
		private TimeSpan retryInterval;
		private DateTime nextRetryTime;
		private int hasChecked;//当切换Log后是否检测过"下次检测时间"

		/// <summary>
		/// 初始化实例
		/// </summary>
		/// <param name="sourceLog">源log</param>
		/// <param name="destinationLog">备用log</param>
		/// <param name="failureTimesLimit">失败次数上限</param>
		/// <param name="retryInterval">重试的等待时间</param>
		public FailoverLogDecorator(TSource sourceLog, TDestination destinationLog, long failureTimesLimit, TimeSpan retryInterval)
		{
			if (sourceLog == null)
				throw new ArgumentNullException("sourceLog");
			if (destinationLog == null)
				throw new ArgumentNullException("destinationLog");
			if (failureTimesLimit <= 0)
				throw new ArgumentOutOfRangeException("failureTimesLimit");

			this.retryInterval = retryInterval;
			this.sourceLog = sourceLog;
			this.destinationLog = destinationLog;
			this.failureTimesLimit = failureTimesLimit;

			//设置sourceLog的TransformLog方式
			this.sourceLog.SetTransformWrite(errorList =>
			{
				foreach (var error in errorList)
				{
					this.destinationLog.Write(this.sourceLog.Transform(error));
				}
			});
		}

		private void ResetLogWay(bool state)
		{
			useDestinationLog = state;
			Interlocked.Exchange(ref this.failedTimes, 0);

			Trace("[[" + useDestinationLog.ToString() + "]]");
		}

		[Conditional("DEBUG")]
		private static void Trace(string s)
		{
			Console.WriteLine("_______" + Thread.CurrentThread.Name + ":  {0}", s);
		}

		/// <summary>
		/// 延长下次检查的时间
		/// </summary>
		private void ExtendNextRetryTime()
		{
			this.nextRetryTime = DateTime.Now.AddTicks(retryInterval.Ticks);
			//Interlocked.CompareExchange(ref hasChecked, 0, 1);
			Interlocked.Exchange(ref hasChecked, 0);
		}

		public override void Write(Error errorEntry)
		{
			//如果已经切换Log了
			if (useDestinationLog)
			{
				//如果超过了需要再次检查的时间,则仍然使用原始Log
				if (DateTime.Now > nextRetryTime)
				{
					if (hasChecked == 1)
					{
						return;
					}
					//标识"下次检测时间"为1表示已经检测过
					//Interlocked.CompareExchange(ref hasChecked, 1, 0);
					Interlocked.Exchange(ref hasChecked, 1);
					Trace("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  set hasChecked to 1");

					Trace(">>>check time: " + DateTime.Now.ToString() + "   -    " + nextRetryTime.ToString());
					try
					{
						this.sourceLog.Write(errorEntry);
						if (this.sourceLog.GetWriteState())
						{
							Trace("try db");
							//如果原始Log没有问题正常了,切换回去
							//并重置错误次数计数
							ResetLogWay(false);
						}
					}
					catch (Exception ex)
					{
						//如果仍然有错误,使用备用日志方式
						this.destinationLog.Write(this.sourceLog.Transform(errorEntry));
						//延长下次检查的时间
						ExtendNextRetryTime();
						//同时记录下这次错误
						this.destinationLog.Write(new Error(ex, LogLevel.Fatal));
					}
				}
				else	//如果还没到检查时间,仍然使用备用Log记录错误
				{
					this.destinationLog.Write(this.sourceLog.Transform(errorEntry));
				}
			}
			else	//如果还没有切换到备用Log
			{
				//尝试使用原始Log记录错误
				try
				{
					this.sourceLog.Write(errorEntry);
					if (this.sourceLog.GetWriteState())
					{
						//这里不要lock了,因为这个方法可能频繁执行
						if (this.failedTimes > 0)
						{
							//add by: fanweixiao
							//如果调用成功的话要将failedTimes重置为0
							Interlocked.Exchange(ref this.failedTimes, 0);
							Trace("rest failedTimes=0");
						}
					}
				}
				catch (Exception ex)
				{
					Trace("db error occured");
					//如果出现异常的次数超过了限制,则开始启用备用Log
					if (Interlocked.Read(ref this.failedTimes) >= this.failureTimesLimit)
					{
						Trace("<<<<<<<<<<<<<<<<<<<<<<<<<<<file>>>>>>>>>>>>>>>>>>>>>>>");
						ResetLogWay(true);
						//计算下一次检查时间
						ExtendNextRetryTime();
						//
						Trace("change next retry time to :" + this.nextRetryTime);
					}
					else	//如果还没有超过限制次数则+1
					{
						Interlocked.Increment(ref this.failedTimes);
						Trace(Interlocked.Read(ref failedTimes).ToString() + "/" + this.failureTimesLimit.ToString());
					}

					//记录下这次错误
					this.destinationLog.Write(this.sourceLog.Transform(new Error(ex, LogLevel.Fatal)));
				}
			}
		}
	}

	/// <summary>
	/// 可以使用备份Log的接口
	/// </summary>
	public interface ITransformable
	{
		/// <summary>
		/// 将<see cref="Error"/>从一种log需要的格式转换为另一种格式
		/// </summary>
		/// <param name="source"><see cref="Error"/>错误</param>
		/// <returns>转后后的错误</returns>
		Error Transform(Error source);

		/// <summary>
		/// 当出现错误时使用备份log的记录方法
		/// </summary>
		/// <param name="transformWrite">记录错误的方式</param>
		void SetTransformWrite(Action<List<Error>> transformWrite);

		/// <summary>
		/// 给外界提供本次操作的成功状态
		/// </summary>
		/// <returns>true表示执行成功</returns>
		/// <remarks>
		/// 这种方式避免了使用Exception来做状态标识所付出的代价
		/// </remarks>
		bool GetWriteState();
	}
}
